iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Modern Web

深入淺出,完整認識 Next.js 13 !系列 第 13

Day 13 - 怎麼限定模組使用環境? Server Components 使用第三方套件要注意什麼?

  • 分享至 

  • xImage
  •  

昨天分享了兩個,假如 components 中同時要有 Client Components 和 Server Components ,如何組合兩者官方提供的 best practices,今天要跟大家分享另外兩個 best practices,分別是 Server-Only 的程式碼留在 server-side 就好,和在 Server Comoponents 使用第三方套件的建議。


Server-Only 的程式碼留在 server-side 就好
有些 functions 假如只會在 Server Components 中使用,我們可以盡可能防止它在 client-side 也可以被呼叫,防止機密資料外洩。

舉例來說,有個負責處理 API 的 getData() function,我們需要在 request headers 中帶入環境變數 API_KEY。這時我們在環境變數的命名上,可以避免使用 NEXT_PUBLIC 開頭,讓這個環境變數只能在 server-side 使用。假如 API_KEY 不是 NEXT_PUBLIC 開頭,它在 client-side 會變成一個空字串,所以 getData() 在 client-side 就無法正常運行。

export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

除了環境變數命名的設計以外,也可以透過 server-only 這個 npm package 來限定某個模組只能在 Server Components 使用。方法很簡單,只需透過 npm 安裝:

npm install server-only

接著 import 進要使用它的模組中就完成了!

import 'server-only'
 
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

這時假如你不小心在 Client Components 中使用了 server-only 的模組,就會跳出錯誤提示:
https://ithelp.ithome.com.tw/upload/images/20230913/20161853bOETFWQLha.png

同理,你也可以安裝 client-only 這個 package 來防止不小心在 Server Components 中使用到 client-only 的模組。

在 Server Components 使用第三方套件的建議
因為目前 App Router 還是相對較新的架構,而且大部分 React 生態系中的套件,尤其是 React component libraries,都會使用到 useState, useEffect, context 等等 client-only features,多數團隊也還沒在使用到這些 features 的 components 中標示 ‘use client’ 來切分 Client Components 與 Server Components 的 boundries,因此假如直接 import 進 Server Component 中可能會發生錯誤。

我們來看官方範例:
假如我們直接從 UI library import 一個用了 useState 的 component <Carousel>,因為 <Carousel> 或是它的父層都沒有標記 'use client',來告訴 React 這邊是 Client Components 的進入點,React 會嘗試在 React Server 渲染它,這時就會出現 useState 不能在 Server Components 使用的錯誤。

/* app/page.tsx */
import { Carousel } from 'acme-carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/* Error: `useState` can not be used within Server Components */}
      <Carousel />
    </div>
  )
}

那假如現在想在 Server Components 中使用有 client-only features 的第三方套件,該怎麼辦呢?如同上述提到,問題出在套件沒有劃出 Client Components 跟 Server Components 的界線,那我們就自己來劃。

我們只需要建一個 Client Component,並標記 'use client' 來當作 Client Component 的進入點,再將 <Carousel> import 進來:

/* app/Carousel.tsx */
'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel

接著在 Server Components 中 import 我們創建的 Carousel component 就可以正常使用該套件了!

但這時你可能會問,假如我們要使用 component libraries,很高機率都是要 import 一些有互動性的 components,不太會在 Server Components 使用吧?的確,多數情況 component libraries 會在 Client Components 中使用,所以不會碰到 boudaries 的問題,但假如要使用 context provider 來調整一些設定時,因為需要吃到 context 的 components 都要包在 provider 中,就有可能碰到上述提到的問題。

比方說我們想在 root layout 中建一個 ThemeContext 來讓所有 components 都能吃到這個 context,但 root layout 必須是 Server Component,就會出現不能在 Server Components 使用 client-only 的 createContext hook 的錯誤:

/* app/layout.tsx */
import { createContext } from 'react'
 
//  createContext is not supported in Server Components
export const ThemeContext = createContext({})
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
      </body>
    </html>
  )
}

這時就會需要透過前面提到的方法,自己建立一個 Client Component 再 import 進 root layout 中:

/* app/theme-provider.tsx */
'use client'
 
import { createContext } from 'react'
 
export const ThemeContext = createContext({})
 
export default function ThemeProvider({ children }) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
/* app/layout.tsx */
import ThemeProvider from './theme-provider'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

還記得昨天有提到,'use client'不是宣告單一 component 為 Client Component,而是宣告 Server Components 和 Client Components 的分界嗎?所以一旦劃下這個分界,分界下的 components 會自動宣告為 Client Components。所以依照盡量讓 Client Components 是 leaf components 的官方建議,假如要使用 context provider,也要盡量讓 provider 接近 Client Components 的真實進入點。


以上就是幾個關於 Server Components 和 Client Components 使用官方提供的 best practices,目前官方文件也持續在編修中,假如我有看到新的內容會再與大家更新!

謝謝大家耐心的閱讀,我們明天見!


上一篇
Day 12 - Next.js 13 Server Components 和 Client Components 組合使用該注意什麼?
下一篇
Day 14 - local storage is not defined 與 Text content does not match server-rendered HTML 錯誤
系列文
深入淺出,完整認識 Next.js 13 !30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言